package com.tpvlog.dfs.namenode.log;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Edits Log双缓冲区
 *
 * @author Ressmix
 */
public class DoubleBuffer {

    // 单块缓冲区大小：默认512kb
    private static final Integer EDIT_LOG_BUFFER_LIMIT = 512 * 1024;

    // 当前正在写入log的缓存区
    private EditLogBuffer curBuffer = new EditLogBuffer();

    // 当前正在刷盘的缓存区
    private EditLogBuffer syncBuffer = new EditLogBuffer();

    // 当前缓冲区对应的日志文件的第一条Log txid
    private Long startTxid = 1L;

    // 已经刷入磁盘的txid范围
    private List<String> flushedTxids = new CopyOnWriteArrayList<>();

    public void write(EditLog log) throws Exception {
        curBuffer.write(log);
    }

    /**
     * 判断当前是否需要刷盘
     * <p>
     * 如果写入缓冲区满了，就需要刷盘
     */
    public boolean shouldSyncToDisk() {
        return curBuffer.size() >= EDIT_LOG_BUFFER_LIMIT;
    }

    /**
     * 交换缓冲区
     */
    public void swapBuffer() {
        EditLogBuffer tmp = curBuffer;
        curBuffer = syncBuffer;
        syncBuffer = tmp;
    }

    /**
     * 将syncBuffer缓冲区中的数据刷入磁盘
     */
    public void flush() throws Exception {
        syncBuffer.flush();
        syncBuffer.clear();
    }

    /**
     * 获取已经刷入磁盘的txid索引
     * @return
     */
    public List<String> getFlushedTxids() {
        return flushedTxids;
    }

    /**
     * 获取当前缓冲区里的数据
     */
    public String[] getBufferedEditsLog() {
        if (curBuffer.size() == 0) {
            return null;
        }
        String editsLogRawData = new String(curBuffer.getBufferData());
        return editsLogRawData.split("\n");
    }

    /**
     * 单块edits log缓冲区
     */
    class EditLogBuffer {
        ByteArrayOutputStream buffer;
        // 最新写入的log日志的txid
        long endTxid = 0L;

        public EditLogBuffer() {
            this.buffer = new ByteArrayOutputStream(EDIT_LOG_BUFFER_LIMIT);
        }

        /**
         * 将editslog日志写入缓冲区
         */
        public void write(EditLog log) throws Exception {
            endTxid = log.getTxid();
            buffer.write(log.getContent().getBytes());
            buffer.write("\n".getBytes());
            System.out.println("写入一条editslog：" + log.getContent() + "，当前缓冲区的大小是：" + size());
        }

        public Integer size() {
            return buffer.size();
        }

        /**
         * 将当前缓存区的数据刷入磁盘
         */
        public void flush() throws Exception {
            byte[] data = buffer.toByteArray();
            ByteBuffer dataBuffer = ByteBuffer.wrap(data);

            // 这里可配置化，我直接写本地
            String editsLogFilePath ="C:\\Users\\Ressmix\\Desktop\\editslog\\edits-" + startTxid + "-" + endTxid + ".log";
            flushedTxids.add(startTxid + "_" + endTxid);

            RandomAccessFile file = null;
            FileOutputStream out = null;
            FileChannel editsLogFileChannel = null;

            try {
                file = new RandomAccessFile(editsLogFilePath, "rw"); // 读写模式，数据写入缓冲区中
                out = new FileOutputStream(file.getFD());
                editsLogFileChannel = out.getChannel();
                editsLogFileChannel.write(dataBuffer);
                // 强制刷盘
                editsLogFileChannel.force(false);
            } finally {
                if (out != null) {
                    out.close();
                }
                if (file != null) {
                    file.close();
                }
                if (editsLogFileChannel != null) {
                    editsLogFileChannel.close();
                }
            }
            // 新的日志文件的第一条log txid
            startTxid = endTxid + 1;
        }

        /**
         * 清空内存缓冲区中的数据
         */
        public void clear() {
            buffer.reset();
        }

        /**
         * 获取内存缓冲区中的数据
         */
        public byte[] getBufferData() {
            return buffer.toByteArray();
        }
    }
}